/*
 * =====================================================================================
 *
 *       Filename:  parse_log.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  12/11/2010 03:52:48 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Chen Ken (Ken), zlchen.ken@gmail.com
 *        Company:  EMC
 *
 * =====================================================================================
 */

#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "tw_list.h"

#define JHASH_GOLDEN_RATIO	0x9e3779b9
#define MAX_LOG_BUFFER 0x4000
#define TW_LOG_CAT_MAX 8
#define MAX_MSG_COUNT_BUFFER 65536*4 
char* g_tw_log_filename = "asnc.log";
time_t g_start_time;
time_t g_last_show_time;

char *log_cat_name[] =
{
    "ERROR",
    "INFO",
    "DEBUG",
    "FUNCTION",
    "OBJECT",
    "PACKET",
    "EMERG",
    "ALERT"
};

/*  log_msg_conuter struct for CLI command: show log_msg_counter */
typedef struct tw_log_entry_s
{
  struct tw_log_entry_s  *next;  /*  link list for next node */
  char category[16];            /*  message category, ie:"ERROR"/"DEBUG"/"INFO"/"FUNCTION"/"EMERG"/"ALERT" */
  int cnt;                      /*  message counter */
  int msg_length;               /*  message length */
  char msg[1];                    /*  message content pointer */
}tw_log_entry; 

tw_list_t* tw_log_table[MAX_MSG_COUNT_BUFFER] = {NULL};

#define __jhash_mix(a, b, c) \
{ \
  a -= b; a -= c; a ^= (c>>13); \
  b -= c; b -= a; b ^= (a<<8); \
  c -= a; c -= b; c ^= (b>>13); \
  a -= b; a -= c; a ^= (c>>12);  \
  b -= c; b -= a; b ^= (a<<16); \
  c -= a; c -= b; c ^= (b>>5); \
  a -= b; a -= c; a ^= (c>>3);  \
  b -= c; b -= a; b ^= (a<<10); \
  c -= a; c -= b; c ^= (b>>15); \
}

static inline uint32_t jhash(const void *key, uint32_t length, uint32_t initval)
{
	uint32_t a, b, c, len;
	const uint8_t *k = key;

	len = length;
	a = b = JHASH_GOLDEN_RATIO;
	c = initval;

	while (len >= 12) {
		a += (k[0] +((uint32_t)k[1]<<8) +((uint32_t)k[2]<<16) +((uint32_t)k[3]<<24));
		b += (k[4] +((uint32_t)k[5]<<8) +((uint32_t)k[6]<<16) +((uint32_t)k[7]<<24));
		c += (k[8] +((uint32_t)k[9]<<8) +((uint32_t)k[10]<<16)+((uint32_t)k[11]<<24));

		__jhash_mix(a,b,c);

		k += 12;
		len -= 12;
	}

	c += length;
	switch (len) {
	case 11: c += ((uint32_t)k[10]<<24);
	case 10: c += ((uint32_t)k[9]<<16);
	case 9 : c += ((uint32_t)k[8]<<8);
	case 8 : b += ((uint32_t)k[7]<<24);
	case 7 : b += ((uint32_t)k[6]<<16);
	case 6 : b += ((uint32_t)k[5]<<8);
	case 5 : b += k[4];
	case 4 : a += ((uint32_t)k[3]<<24);
	case 3 : a += ((uint32_t)k[2]<<16);
	case 2 : a += ((uint32_t)k[1]<<8);
	case 1 : a += k[0];
	};

	__jhash_mix(a,b,c);

	return c;
}

uint32_t tw_hash(const void * key, size_t length, uint32_t initval)
{
  return jhash( key, length, initval );
}

int tw_convert_str2cat( char* category, int n )
{
  int i = 0;
  for( ; i < TW_LOG_CAT_MAX; ++i ) 
  {
    if( strncasecmp(log_cat_name[i], category, n) == 0 )
    {
      return i;
    }
  }

  return -1;
}

static tw_bool tw_log_same_msg_cnt(const char* msg, tw_list_t* tlog_list)
{
  int msg_length = strlen(msg);
  tw_log_entry* tlog_entry = (tw_log_entry*)tw_list_gethead(tlog_list);
  while( NULL != tlog_entry )
  {
    if( (msg_length == tlog_entry->msg_length) && (0 == strcmp(msg, tlog_entry->msg)) )
    {
      tlog_entry->cnt ++;
      return TRUE;
    }
    tlog_entry = tlog_entry->next;
  }
  return FALSE;
}

static tw_bool tw_log_insert_log_entry( tw_list_t* tlog_list, char* category, const char* log_msg )
{
  tw_log_entry* tlog_entry = (tw_log_entry*)calloc( 1, (sizeof(tw_log_entry) + strlen(log_msg) + 1) );
  if(NULL == tlog_entry )
  {
     return FALSE;
  }

	strncpy( tlog_entry->category, category, sizeof(tlog_entry->category) - 1 );
  tlog_entry->category[sizeof(tlog_entry->category) - 1] = '\0';
	tlog_entry->cnt = 1;
  tlog_entry->msg_length = strlen(log_msg);

	strcpy( tlog_entry->msg, log_msg );

  tw_list_addtail(tlog_list, (tw_list_node_p)tlog_entry);

	return TRUE;
}

/*Tue Jan 11 16:01:39 2011 [DEBUG] Insert this QoS Profile (id=0) to Inuse QoS database*/

static int trace_log_entry( char* spos, int len )
{
	/* Extract the "category" and "msg" fields */
  char log_msg[MAX_LOG_BUFFER];
  char category[16];
  char* tim_pos = NULL;
  char* cat_epos = NULL; 
	tw_bool res = FALSE;
  int log_msglen = 0;

	if( (tim_pos = memchr(spos, '[', len)) == NULL )
	{
		/* this is not a valid log entry */
		return 0;
	}

  if( (cat_epos = memchr(tim_pos, ']', len - (tim_pos - spos + 1))) == NULL )
	{
		/* this is also not a valid log entry */
		return 0;
	}

	if( tw_convert_str2cat(tim_pos + 1, cat_epos - tim_pos -1) < 0 )
	{
		/* We don't care this category, ignore it */
		return 0;
	}

	/* Convert time str to time_t, and compare with g_last_show_time  that CLI did a show log
	 * If the log time read from log file is earlier than g_last_show_time, ignore the log entry,
	 * because we already show the cnt for the log already */

  snprintf( category, cat_epos - tim_pos, "%s", tim_pos + 1 );
  category[cat_epos - tim_pos + 1] = '\0';

  log_msglen = len - (cat_epos - spos + 1);
  snprintf( log_msg, log_msglen, "%s", cat_epos + 1 );
  log_msg[log_msglen + 1] = '\0';

  unsigned int hash_code = tw_hash( log_msg, log_msglen, 0 );
	hash_code &= 0x3FFFF;

  if(NULL == tw_log_table[hash_code] )
  {
    tw_list_t* tlog_list = (tw_list_t*)calloc( 1, sizeof(tw_list_t) );
	  if(NULL == tlog_list )
	  {
      return FALSE;
    }
    tw_list_init(tlog_list, NULL);

  	res = tw_log_insert_log_entry( tlog_list, category, log_msg );
  	if( FALSE == res )
  	{
  		free( tlog_list );
  		return FALSE;
  	}
    tw_log_table[hash_code] = tlog_list;
  }
  else
  {
    res = tw_log_same_msg_cnt( log_msg, tw_log_table[hash_code] );
    if( FALSE == res )
    {
      res = tw_log_insert_log_entry( tw_log_table[hash_code], category, log_msg );
    }
  }

  return res;
}

static int do_trace_log( char* log_buf, size_t bytes )
{
	char  log_entry[MAX_LOG_BUFFER];
	char* tok_pos = log_buf;
	char* spos = log_buf;

	do{
    tok_pos = memchr( spos, '\n', bytes);
	  if( tok_pos != NULL )
		{
      trace_log_entry( spos, tok_pos - spos + 1 );
		  bytes -= ( tok_pos - spos + 1 );
      spos = tok_pos + 1;
		}
	}while( tok_pos != NULL );
}

static int cli_trace_log( void )
{ 
	int bytes_left_over = 0;
  char* tok_pos = NULL;
	char buf[1024];

	FILE* tw_log_f = fopen( g_tw_log_filename, "r" );
	if( tw_log_f == NULL )
	{
		return 1;
	}

	while( !feof( tw_log_f ) )
	{
    size_t read_bytes = fread( buf + bytes_left_over , 1, sizeof(buf) - bytes_left_over, tw_log_f );
    if( read_bytes > 0 )
		{
		  int bytes_in_buffer = read_bytes + bytes_left_over;
      tok_pos = memrchr( buf, '\n',  bytes_in_buffer );
			if( tok_pos != NULL )
			{
				bytes_left_over = buf + bytes_in_buffer - tok_pos - 1;
  		  do_trace_log( buf, bytes_in_buffer - bytes_left_over );
				bcopy( buf, tok_pos+1, bytes_left_over );
			}
		}
  }

	fclose( tw_log_f );

	return 0;
}

int cli_show_log_cnt( void )
{
  int i = 0;
  int msg_counter = 0;
  tw_list_t* tw_log_list = NULL;
  tw_log_entry* entry = NULL;

  for( ; i < MAX_MSG_COUNT_BUFFER; ++i )
	{
    tw_log_list = tw_log_table[i];
    if( (NULL == tw_log_list) || (0 == tw_log_list->count) )
    {
      continue;
    }

    entry = (tw_log_entry*)tw_list_gethead(tw_log_list);
    while(NULL != entry)
	  {
      msg_counter ++;
	    fprintf(stdout, "%-5d|%-5d|%-5d|%5s|%s\n", 
					msg_counter, (i+1), entry->cnt, entry->category, entry->msg );
      entry = (tw_log_entry*)(entry->next);
	  }
	}

  return TRUE;
}

int main( int argc, char* argv[] )
{
	cli_trace_log();
  cli_show_log_cnt();

	return 0;
}

